pub mod custom;

use super::{
    err, err_value, json, ok, throw_err, Base, CSend, Client, Collect, ECode, KeyNode, Lock, MapSv,
    Resp, SStr, SvCache, VBase, VMap, Value, VecV,
};
use std::collections::HashMap;

pub struct Code;

impl Base for Code {}

impl Code {
    pub async fn do_cmd(
        &self,
        para: &Value,
        cmd: &str,
        client: &Client,
        cache: SvCache,
        lock: Lock,
        tx: CSend,
    ) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        let collect = Collect::new(client, cache, line);

        match cmd {
            "getpsn" => self.parse_psn(data, lock, &collect, &tx).await,
            "gen" => self.gen_code(data, lock, &collect).await,
            _ => collect.do_cmd("code", cmd, para, lock, None).await,
        }
    }

    async fn gen_code(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>) -> Resp {
        let _ = lock.lock().await;
        let rule = data.ck_str("rule")?;

        let mut ret = HashMap::new();
        let mut md = VecV::new();

        let psn = custom::gen_psn(rule, collect, &mut md).await?;
        ret.insert("psn", psn);

        self.update_num(&md, collect).await?;

        ok!(ret)
    }

    async fn parse_psn(&self, data: &MapSv, lock: Lock, collect: &Collect<'_>, tx: &CSend) -> Resp {
        let wsn = data.ck_str("wsn")?;
        let pallet = match data.k_str("pallet") {
            Some(v) => v,
            None => "-",
        };
        let pure = match data.k_bool("pure") {
            Some(v) => v,
            None => false,
        };

        let _ = lock.lock().await;
        let mut del = SStr::new();

        let find = match data.k_str("task") {
            Some(task_sn) => self.find_psn(task_sn, pure, collect).await?,
            None => match self.find_cur_task(pure, collect, &mut del).await {
                Ok(v) => v,
                Err(e) => {
                    let k_cur_task = collect.key_sn("cur", "task")?;
                    let cmd = json!({"curtask": ""});
                    collect.update_file("cur", &k_cur_task, &cmd).await?;

                    collect.del_run_set(&del, tx).await?;

                    return Err(e);
                }
            },
        };

        collect.del_run_set(&del, tx).await?;

        let ret = find.ck_str("ret")?;
        let tech_sn = find.ck_str("tech")?;
        let task_sn = find.ck_str("task")?;

        let mut gen = HashMap::new();

        if ret == "ok" {
            let psn = find.ck_str("psn")?;

            let cmd = json!({"agv": pallet});
            collect.md_item("psn", psn, &cmd).await?;

            gen.insert("psn".to_string(), json!(psn));
        } else if ret == "gen" {
            let tmp = self.gen_psn(wsn, tech_sn, collect).await?;
            let tmp = tmp.ckobj()?;

            for (k, v) in tmp.iter() {
                gen.insert(k.to_string(), v.clone());
            }

            if let Some(psn) = tmp.k_str("psn") {
                let cmd = if pure {
                    json!({"v": {
                        "sn": psn,
                        "task": task_sn
                    }})
                } else {
                    json!({"v": {
                        "sn": psn,
                        "task": task_sn,
                        "agv": pallet
                    }})
                };

                let k_task = collect.key_sub_sn("task", "psn", &task_sn)?;
                collect.add_item_sub("psn", &k_task, &cmd).await?;
            }
        }

        gen.insert("task".to_string(), json!(task_sn));
        gen.insert("tech".to_string(), json!(tech_sn));

        ok!(gen)
    }

    async fn find_psn(&self, task_sn: &str, pure: bool, collect: &Collect<'_>) -> Resp {
        let task = collect.item_sn("task", task_sn).await?;
        let pnum = task.ck_i64("pnum")?;
        let tech_sn = task.ck_str("tech")?;

        match collect.content_sub("task", "psn", task_sn).await {
            Err(_) => {
                if pnum > 0 {
                    return Ok(json!({
                        "ret": "gen",
                        "task": task_sn,
                        "tech": tech_sn,
                    }));
                }
            }
            Ok(code) => {
                let num = code.len() as i64;

                if pure {
                    if pnum > num {
                        return Ok(json!({
                            "ret": "gen",
                            "task": task_sn,
                            "tech": tech_sn,
                        }));
                    } else {
                        throw_err!(ECode::Code, "no psn");
                    }
                }

                for c in code.iter() {
                    if let Ok(body) = collect.item_sn("psn", c).await {
                        let body = body.ckobj()?;

                        if let Some(state) = body.k_str("state") {
                            if state == "pause" {
                                continue;
                            }
                        }

                        if !body.contains_key("agv") {
                            let psn = body.ck_str("sn")?;

                            return Ok(json!({
                                "ret": "ok",
                                "psn": psn,
                                "task": task_sn,
                                "tech": tech_sn,
                            }));
                        }
                    }
                }

                if pnum > num {
                    return Ok(json!({
                        "ret": "gen",
                        "task": task_sn,
                        "tech": tech_sn,
                    }));
                }
            }
        }

        err!(ECode::Code, "no psn")
    }

    async fn gen_psn(&self, wsn: &str, tech_sn: &str, collect: &Collect<'_>) -> Resp {
        let tech = collect.item_sn("tech", tech_sn).await?;
        let tech = tech.ckobj()?;

        let code = tech.ck_obj("code")?.ck_obj(wsn)?;

        let mut ret = HashMap::new();
        let mut md = VecV::new();

        if let Some(rule) = code.k_str("rule") {
            let psn = custom::gen_psn(rule, collect, &mut md).await?;
            ret.insert("psn", psn);
        }

        if let Some(ext) = code.k_array("ext") {
            for d in ext.iter() {
                let d = d.ckobj()?;

                let tag = d.ck_str("sn")?;
                let rule = d.ck_str("rule")?;

                let psn = custom::gen_psn(rule, collect, &mut md).await?;
                ret.insert(tag, psn);
            }
        }

        self.update_num(&md, collect).await?;

        Ok(json!(ret))
    }

    async fn update_num(&self, md: &VecV, collect: &Collect<'_>) -> Result<(), Value> {
        for d in md.iter() {
            let d = d.ckobj()?;

            let sn = d.ck_str("sn")?;
            let val = d.ck_val("val")?;

            if let Some(_) = d.k_bool("update") {
                collect.update_file("cur", sn, val).await?;
            } else {
                let tp = d.ck_str("tp")?;
                collect.md_item(tp, sn, val).await?;
            }
        }

        Ok(())
    }

    async fn get_cur_task(&self, pure: bool, collect: &Collect<'_>) -> Resp {
        let k_cur_task = collect.key_sn("cur", "task")?;

        let body = collect.get_file("cur", &k_cur_task)?;
        let task_sn = body.ck_str("curtask")?;

        if task_sn.is_empty() {
            throw_err!(ECode::Code, "no cur task");
        }

        let task = collect.item_sn("task", task_sn).await?;
        let state = task.ck_str("state")?;

        if state != "open" {
            let cmd = json!({"curtask": ""});
            collect.update_file("cur", &k_cur_task, &cmd).await?;

            throw_err!(ECode::Code, "no open task");
        }

        self.find_psn(task_sn, pure, collect).await
    }

    async fn find_cur_task(&self, pure: bool, collect: &Collect<'_>, del: &mut SStr) -> Resp {
        if let Ok(body) = self.get_cur_task(pure, collect).await {
            return Ok(body);
        }

        let run = collect.content_run("task").await?;

        for d in run.iter() {
            let task_sn = &d[20..];

            let k_cur_task = collect.key_sn("cur", "task")?;
            let cmd = json!({"curtask": task_sn});
            collect.update_file("cur", &k_cur_task, &cmd).await?;

            if let Ok(v) = self.find_psn(task_sn, pure, collect).await {
                return Ok(v);
            }

            del.insert(task_sn.to_string());
        }

        err!(ECode::Code, "no find task")
    }
}
